﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lucene.Net.Documents;
using System.ComponentModel;
using System.Reflection;
using ObjectSearch.Configuration;
using System.IO;

namespace ObjectSearch.Converter
{
    public class ObjectToDocumentConverter
    {
        public static Document Convert(object obj, Configuration.Configuration configuration)
        {
            Document luceneDocument = new Document();

            ObjectMetaData meta = configuration.FindMetaData(obj.GetType());

            if (meta == null)
                throw new LuceneException(string.Format("Unable to find configuration for the {0} type.", obj.GetType().FullName));


            foreach (var f in meta.Fields)
            {
                object value = GetLuceneValue(obj, f);
                luceneDocument.Add(CreateField(value, f));
            }

            return luceneDocument;
        }


        private static Field CreateField(object value, FieldMetaData fieldMetaData)
        {
            Field f = null;
            string name = fieldMetaData.FieldName;
            Field.Index index = GetIndex(fieldMetaData.IndexOptions);
            Field.Store store = GetStorageOptions(fieldMetaData.StorageOptions);
            Field.TermVector termVector = GetTermVector(fieldMetaData.TermVectorOptions);

            if (value is string)
            {
                f = new Field(fieldMetaData.FieldName, value as string, store, index, termVector);
            }
            if (value is byte[])
            {
                f = new Field(fieldMetaData.FieldName, (byte[])value, store);
            }
            if (value is TextReader)
            {
                f = new Field(fieldMetaData.FieldName, value as TextReader, termVector);
            }

            return f;
        }
        private static object GetLuceneValue(object obj)
        {
            if (obj is string)
            {
                return obj;
            }

            if (obj is string[])
            {
                return string.Join(", ", (string[])obj);
            }

            if (obj is byte[])
            {
                return obj;
            }

            if (obj is byte)
            {
                return int.Parse(obj.ToString());
            }

            if (obj is DateTime)
            {
                //return Lucene.Net.Documents.DateField.DateToString((DateTime)obj);
                return DateTools.DateToString((DateTime)obj, DateTools.Resolution.MILLISECOND);
            }

            return null;
        }
        private static object GetLuceneValue(object containingObject, FieldMetaData fieldData)
        {
            Type objectType = containingObject.GetType();
            string memberName = fieldData.MemberName;

            object value = GetMemberValue(containingObject, memberName);

            //look for a type convert attribute on the member itself
            TypeConverter tc = GetTypeConverterFromAttribute(objectType, memberName);

            if (tc != null)
            {
                return tc.ConvertToString(value);
            }

            //we don't have a converter on the member itself, use the standard method
            object returnedObject = GetLuceneValue(value);

            if (returnedObject != null)
                return returnedObject;

            //if the return value from the standard method is null
            //then we don't know how to convert the value,
            //try to find the Standard Type Convert using the TypeDescriptor class.
            if (returnedObject == null)
            {
                Type memberType = GetMemberType(objectType, memberName);
                TypeConverter standardTypeConverter = TypeDescriptor.GetConverter(memberType);

                if (standardTypeConverter != null)
                    return standardTypeConverter.ConvertToString(value);
            }

            throw new LuceneException(string.Format("Cannot determin how to return the value of the object of type {0}, type using the TypeConvertAttribute on the {1} member to fix this issue.", GetMemberType(objectType, memberName).FullName, memberName));
        }

        private static TypeConverter GetTypeConverterFromAttribute(Type objectType, string memberName)
        {
            PropertyInfo propertyInfo = objectType.GetProperty(memberName);
            FieldInfo fieldInfo = objectType.GetField(memberName);

            if (propertyInfo != null)
            {
                object[] customAttributes = propertyInfo.GetCustomAttributes(typeof(TypeConverterAttribute), true);

                if (customAttributes.Length > 0)
                {
                    TypeConverterAttribute attribute = customAttributes[0] as TypeConverterAttribute;
                    TypeConverter converter = Activator.CreateInstance(Type.GetType(attribute.ConverterTypeName)) as TypeConverter;
                    return converter;
                }
            }

            if (fieldInfo != null)
            {
                object[] customAttributes = fieldInfo.GetCustomAttributes(typeof(TypeConverterAttribute), true);

                if (customAttributes.Length > 0)
                {
                    TypeConverterAttribute attribute = customAttributes[0] as TypeConverterAttribute;
                    TypeConverter converter = Activator.CreateInstance(Type.GetType(attribute.ConverterTypeName)) as TypeConverter;
                    return converter;
                }
            }

            return null;
        }
        private static Type GetMemberType(Type objectType, string memberName)
        {
            PropertyInfo propertyInfo = objectType.GetProperty(memberName);
            FieldInfo fieldInfo = objectType.GetField(memberName);

            if (propertyInfo != null)
            {
                return propertyInfo.PropertyType;
            }

            if (fieldInfo != null)
            {
                return fieldInfo.FieldType;
            }

            return null;
        }
        private static object GetMemberValue(object containingObject, string memberName)
        {
            Type objectType = containingObject.GetType();

            PropertyInfo propertyInfo = objectType.GetProperty(memberName);
            FieldInfo fieldInfo = objectType.GetField(memberName);

            if (propertyInfo != null)
            {
                return propertyInfo.GetValue(containingObject, null);
            }

            if (fieldInfo != null)
            {
                return fieldInfo.GetValue(containingObject);
            }

            return null;
        }

        private static Lucene.Net.Documents.Field.Store GetStorageOptions(StorageOptions options)
        {
            switch (options)
            {
                case StorageOptions.Stored:
                    return Field.Store.YES;
                case StorageOptions.Compressed:
                    return Field.Store.COMPRESS;
                case StorageOptions.NotStored:
                    return Field.Store.NO;
                default:
                    throw new LuceneException("Unknown StorageOptions");
            }
        }
        private static Lucene.Net.Documents.Field.Index GetIndex(IndexOptions indexOptions)
        {
            switch (indexOptions)
            {
                case IndexOptions.NotIndexed:
                    return Field.Index.NO;
                case IndexOptions.Tokenized:
                    return Field.Index.TOKENIZED;
                case IndexOptions.Untokenized:
                    return Field.Index.UN_TOKENIZED;
                case IndexOptions.NotOptimized:
                    return Field.Index.NO_NORMS;
                default:
                    throw new LuceneException("Unknown IndexOptions");
            }
        }
        private static Lucene.Net.Documents.Field.TermVector GetTermVector(TermVectorOptions termVectorOptions)
        {
            switch (termVectorOptions)
            {
                case TermVectorOptions.NotStored:
                    return Field.TermVector.NO;
                case TermVectorOptions.Stored:
                    return Field.TermVector.YES;
                case TermVectorOptions.StoreWithPositions:
                    return Field.TermVector.WITH_POSITIONS;
                case TermVectorOptions.StoreWithOffsets:
                    return Field.TermVector.WITH_OFFSETS;
                case TermVectorOptions.StoreWithPositionsAndOffsets:
                    return Field.TermVector.WITH_POSITIONS_OFFSETS;
                default:
                    throw new LuceneException("Unknown TermVectorOptions");
            }
        }
    }
}
